#include "MouseWin32.h"
#include "Utils.h"
#include "common.h"
#include <Windows.h>
#include "WinApi.h"
#include "RunMouse.h"

float MouseWin32::getDPI()
{
    HDC hdcScreen;
    hdcScreen = CreateDCW(L"DISPLAY", NULL, NULL, NULL);

    int logx = GetDeviceCaps(hdcScreen, LOGPIXELSX);
    // int logy = GetDeviceCaps(hdcScreen, LOGPIXELSY);
    // setlog("logx = %d", logx);
    if (NULL != hdcScreen)
    {
        DeleteDC(hdcScreen);
    }
    float dpi = 1.0;
    if (logx == 96)
    {
        dpi = 1.0;
    }
    else if (logx == 120)
    {
        dpi = 1.25;
    }
    else if (logx == 144)
    {
        dpi = 1.50;
    }
    else if (logx == 192)
    {
        dpi = 2.0;
    }

    return dpi;
}

MouseWin32::MouseWin32()
{
    _hwnd = 0;
    _mode = 0;
    _x = 0;
    _y = 0;
    _dpi = getDPI();
}

MouseWin32::~MouseWin32()
{
    _hwnd = NULL;
}

long MouseWin32::Bind(long h, int mode)
{
    _hwnd = h;
    _mode = mode;
    return 1;
}

long MouseWin32::UnBind()
{
    _hwnd = 0;
    _mode = 0;
    return 1;
}

long MouseWin32::GetCursorPos(long &x, long &y)
{
    BOOL ret = FALSE;
    POINT pt;
    ret = ::GetCursorPos(&pt);
    if (_hwnd != (long)::GetDesktopWindow())
    {
        ret = ::ScreenToClient((HWND)_hwnd, &pt);
    }
    x = pt.x;
    y = pt.y;
    return ret;
}

long MouseWin32::MoveR(int rx, int ry)
{
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    {
        // https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/ns-winuser-mouseinput
        _x += rx, _y += ry;

        INPUT Input = {0};
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_MOVE;
        Input.mi.dx = static_cast<LONG>(rx);
        Input.mi.dy = static_cast<LONG>(ry);
        return ::SendInput(1, &Input, sizeof(INPUT)) > 0 ? 1 : 0;
    }
    case INPUT_TYPE::IN_NORMAL3:
    {
        return MoveTo(_x + rx, _y + ry);
    }
    }
    return MoveTo(_x + rx, _y + ry);
}

long MouseWin32::MoveTo(int x, int y)
{
    // 启用DPI感知后，不需要在进行坐标处理
    // x = x * _dpi;
    // y = y * _dpi;
    long ret = 0;
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    {
        POINT pt;
        pt.x = x, pt.y = y;
        if (_hwnd)
            ::ClientToScreen((HWND)_hwnd, &pt);
        x = pt.x, y = pt.y;
        // setlog(L"hwnd:%d,pt:%d,%d",_hwnd, 0, y);
        static double fScreenWidth = ::GetSystemMetrics(SM_CXSCREEN) - 1;
        static double fScreenHeight = ::GetSystemMetrics(SM_CYSCREEN) - 1;
        double fx = x * (65535.0f / fScreenWidth);
        double fy = y * (65535.0f / fScreenHeight);
        INPUT Input = {0};
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE;
        Input.mi.dx = static_cast<LONG>(fx);
        Input.mi.dy = static_cast<LONG>(fy);
        /*The function returns the number of events that it successfully inserted into the keyboard or mouse input stream.
        If the function returns zero, the input was already blocked by another thread.
        To get extended error information, call GetLastError.
        This function fails when it is blocked by UIPI.
        Note that neither GetLastError nor the return value will indicate the failure was caused by UIPI blocking.
        */
        ret = ::SendInput(1, &Input, sizeof(INPUT)) > 0 ? 1 : 0;
        break;
    }
    case INPUT_TYPE::IN_WINDOWS:
    {
        ret = ::SendMessageTimeout((HWND)_hwnd, WM_MOUSEMOVE, 0, MAKELPARAM(x, y), SMTO_BLOCK, 2000, nullptr);
        break;
    }
    case INPUT_TYPE::IN_NORMAL3:
    {
        RunMouse rm;
        rm.MoveMouse(x, y);
        ret = 1;
        break;
    }
    }
    _x = x, _y = y;
    return ret;
}

long MouseWin32::MoveToEx(int x, int y, int w, int h)
{

    if (w >= 2 && h >= 2)
        return MoveTo(x + rand() % w, y + rand() % h);
    else
        return MoveTo(x, y);
}

long MouseWin32::LeftClick()
{
    long ret = 0, ret2 = 0;
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    case INPUT_TYPE::IN_NORMAL3:
    {
        INPUT Input = {0};
        // left down
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
        ret = ::SendInput(1, &Input, sizeof(INPUT));
        ::Delay(MOUSE_NORMAL_DELAY);
        // left up
        ::ZeroMemory(&Input, sizeof(INPUT));
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
        ret2 = ::SendInput(1, &Input, sizeof(INPUT));
        break;
    }
    case INPUT_TYPE::IN_WINDOWS:
    {
        ret = ::SendMessageTimeout((HWND)_hwnd, WM_LBUTTONDOWN, 0, MAKELPARAM(_x, _y), SMTO_BLOCK, 2000, nullptr);
        ::Delay(MOUSE_WINDOWS_DELAY);
        ret = ::SendMessageTimeout((HWND)_hwnd, WM_LBUTTONUP, 0, MAKELPARAM(_x, _y), SMTO_BLOCK, 2000, nullptr);
        /// ret=::PostMessage(_hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(_x, _y));
        // ret = ::SendMessageTimeout(_hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(_x, _y), SMTO_BLOCK, 2000, nullptr);
        // ret = ::SendNotifyMessage(_hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(_x, _y));
        //::Sleep(100);
        // ret = ::SendMessage(_hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(_x, _y));
        // ret2=::SendMessageTimeout(_hwnd, WM_LBUTTONUP, 0, MAKELPARAM(_x, _y), SMTO_BLOCK, 2000, nullptr);
        // ret2 = ::SendMessage(_hwnd, WM_LBUTTONUP, 0, MAKELPARAM(_x, _y));
        //::SendMessage(_hwnd, WM_CAPTURECHANGED, 0, 0);
        break;
    }
    }
    return ret && ret2 ? 1 : 0;
}

long MouseWin32::LeftDoubleClick()
{
    long r1, r2;
    r1 = LeftClick();
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    case INPUT_TYPE::IN_NORMAL3:
    {
        ::Delay(MOUSE_NORMAL_DELAY);
        break;
    }
    case INPUT_TYPE::IN_WINDOWS:
    {
        ::Delay(MOUSE_WINDOWS_DELAY);
        break;
    }
    }
    r2 = LeftClick();
    return r1 && r2 ? 1 : 0;
}

long MouseWin32::LeftDown()
{
    long ret = 0;
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    case INPUT_TYPE::IN_NORMAL3:
    {
        INPUT Input = {0};
        // left down
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
        ret = ::SendInput(1, &Input, sizeof(INPUT));
        break;
    }
    case INPUT_TYPE::IN_WINDOWS:
    {
        ret = ::SendMessageTimeout((HWND)_hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(_x, _y), SMTO_BLOCK, 2000, nullptr);
        break;
    }
    }
    return ret;
}

long MouseWin32::LeftUp()
{
    long ret = 0;
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    case INPUT_TYPE::IN_NORMAL3:
    {
        INPUT Input = {0};
        // left up
        ::ZeroMemory(&Input, sizeof(INPUT));
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
        ret = ::SendInput(1, &Input, sizeof(INPUT));
        break;
    }
    case INPUT_TYPE::IN_WINDOWS:
    {
        ret = ::SendMessageTimeout((HWND)_hwnd, WM_LBUTTONUP, MK_LBUTTON, MAKELPARAM(_x, _y), SMTO_BLOCK, 2000, nullptr);
        break;
    }
    }
    return ret;
}

long MouseWin32::MiddleClick()
{
    long r1, r2;
    r1 = MiddleDown();
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    case INPUT_TYPE::IN_NORMAL3:
    {
        ::Delay(MOUSE_NORMAL_DELAY);
        break;
    }
    case INPUT_TYPE::IN_WINDOWS:
    {
        ::Delay(MOUSE_WINDOWS_DELAY);
        break;
    }
    }
    r2 = MiddleUp();
    return r1 && r2 ? 1 : 0;
}

long MouseWin32::MiddleDown()
{
    long ret = 0;
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    case INPUT_TYPE::IN_NORMAL3:
    {
        INPUT Input = {0};
        // left down
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN;
        ret = ::SendInput(1, &Input, sizeof(INPUT));
        break;
    }

    case INPUT_TYPE::IN_WINDOWS:
    {
        ret = ::SendMessageTimeout((HWND)_hwnd, WM_MBUTTONDOWN, MK_MBUTTON, MAKELPARAM(_x, _y), SMTO_BLOCK, 2000, nullptr);
        break;
    }
    }
    return ret;
}

long MouseWin32::MiddleUp()
{
    long ret = 0;
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    case INPUT_TYPE::IN_NORMAL3:
    {
        INPUT Input = {0};
        // left up
        ::ZeroMemory(&Input, sizeof(INPUT));
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_MIDDLEUP;
        ret = ::SendInput(1, &Input, sizeof(INPUT));
        break;
    }

    case INPUT_TYPE::IN_WINDOWS:
    {
        ret = ::SendMessageTimeout((HWND)_hwnd, WM_MBUTTONUP, MK_MBUTTON, MAKELPARAM(_x, _y), SMTO_BLOCK, 2000, nullptr);
        break;
    }
    }
    return ret;
}

long MouseWin32::RightClick()
{
    long ret = 0;
    long r1, r2;
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    case INPUT_TYPE::IN_NORMAL3:
    {
        INPUT Input = {0};
        // left down
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN;
        r1 = ::SendInput(1, &Input, sizeof(INPUT));
        ::Delay(MOUSE_NORMAL_DELAY);
        // left up
        ::ZeroMemory(&Input, sizeof(INPUT));
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_RIGHTUP;
        r2 = ::SendInput(1, &Input, sizeof(INPUT));
        ret = r1 > 0 && r2 > 0 ? 1 : 0;
        break;
    }
    case INPUT_TYPE::IN_WINDOWS:
    {
        r1 = ::SendMessageTimeout((HWND)_hwnd, WM_RBUTTONDOWN, MK_RBUTTON, MAKELPARAM(_x, _y), SMTO_BLOCK, 2000, nullptr);
        ::Delay(MOUSE_WINDOWS_DELAY);
        r2 = ::SendMessageTimeout((HWND)_hwnd, WM_RBUTTONUP, MK_RBUTTON, MAKELPARAM(_x, _y), SMTO_BLOCK, 2000, nullptr);
        ret = r1 == 0 && r2 == 0 ? 1 : 0;
        break;
    }
    }
    return ret;
}

long MouseWin32::RightDown()
{
    long ret = 0;
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    case INPUT_TYPE::IN_NORMAL3:
    {
        INPUT Input = {0};
        // left down
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN;
        ret = ::SendInput(1, &Input, sizeof(INPUT)) > 0 ? 1 : 0;
        break;
    }
    case INPUT_TYPE::IN_WINDOWS:
    {
        ret = ::SendMessageTimeout((HWND)_hwnd, WM_RBUTTONDOWN, MK_RBUTTON, MAKELPARAM(_x, _y), SMTO_BLOCK, 2000, nullptr);
        break;
    }
    }
    return ret;
}

long MouseWin32::RightUp()
{
    long ret = 0;
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    case INPUT_TYPE::IN_NORMAL3:
    {
        INPUT Input = {0};
        ::ZeroMemory(&Input, sizeof(INPUT));
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_RIGHTUP;
        ret = ::SendInput(1, &Input, sizeof(INPUT)) > 0 ? 1 : 0;
        break;
    }
    case INPUT_TYPE::IN_WINDOWS:
    {
        ret = ::SendMessageTimeout((HWND)_hwnd, WM_RBUTTONUP, MK_RBUTTON, MAKELPARAM(_x, _y), SMTO_BLOCK, 2000, nullptr);
        break;
    }
    }
    return ret;
}

long MouseWin32::WheelDown()
{
    long ret = 0;
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    case INPUT_TYPE::IN_NORMAL3:
    {
        INPUT Input = {0};
        // down
        /*
        If dwFlags contains MOUSEEVENTF_WHEEL, then dwData specifies the amount of wheel movement.
        A positive value indicates that the wheel was rotated forward, away from the user;
        a negative value indicates that the wheel was rotated backward, toward the user.
        One wheel click is defined as WHEEL_DELTA, which is 120.
        */
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_WHEEL;
        Input.mi.mouseData = -WHEEL_DELTA;
        ret = ::SendInput(1, &Input, sizeof(INPUT)) > 0 ? 1 : 0;
        break;
    }

    case INPUT_TYPE::IN_WINDOWS:
    {
        /*
        wParam
        The high-order word indicates the distance the wheel is rotated,
        expressed in multiples or divisions of WHEEL_DELTA, which is 120.
        A positive value indicates that the wheel was rotated forward, away from the user;
        a negative value indicates that the wheel was rotated backward, toward the user.
        The low-order word indicates whether various virtual keys are down.
        This parameter can be one or more of the following values.
        lParam
        The low-order word specifies the x-coordinate of the pointer,
        relative to the upper-left corner of the screen.
        The high-order word specifies the y-coordinate of the pointer,
        relative to the upper-left corner of the screen.
        */
        // If an application processes this message, it should return zero.
        POINT pt{};
        pt.x = _x, pt.y = _y;
        ::ClientToScreen((HWND)_hwnd, &pt);
        ret = ::SendMessageTimeout((HWND)_hwnd, WM_MOUSEWHEEL, MAKEWPARAM(0, -WHEEL_DELTA), MAKELPARAM(pt.x, pt.y), SMTO_BLOCK, 2000, nullptr);
        break;
    }
    }

    return ret;
}

long MouseWin32::WheelUp()
{
    long ret = 0;
    switch (_mode)
    {
    case INPUT_TYPE::IN_NORMAL:
    case INPUT_TYPE::IN_NORMAL3:
    {
        INPUT Input = {0};
        // left up
        Input.type = INPUT_MOUSE;
        Input.mi.dwFlags = MOUSEEVENTF_WHEEL;
        Input.mi.mouseData = WHEEL_DELTA;
        ret = ::SendInput(1, &Input, sizeof(INPUT)) > 0 ? 1 : 0;
        break;
    }

    case INPUT_TYPE::IN_WINDOWS:
    {
        POINT pt{};
        pt.x = _x, pt.y = _y;
        ::ClientToScreen((HWND)_hwnd, &pt);
        ret = ::SendMessageTimeout((HWND)_hwnd, WM_MOUSEWHEEL, MAKEWPARAM(0, WHEEL_DELTA), MAKELPARAM(pt.x, pt.y), SMTO_BLOCK, 2000, nullptr);
        break;
    }
    }
    return ret;
}